﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection.Emit;

namespace Reflective
{
    /// <summary>
    /// Extension methods for <see cref="Type"/>.
    /// </summary>
    public static class TypeExtensions
    {
        private static readonly Dictionary<Type, string> CSharpKeywords = new Dictionary<Type, string>
        {
            { typeof(string), "string" },
            { typeof(char), "char" },
            { typeof(bool), "bool" },
            { typeof(int), "int" },
            { typeof(uint), "uint" },
            { typeof(long), "long" },
            { typeof(ulong), "ulong" },
            { typeof(byte), "byte" },
            { typeof(sbyte), "sbyte" },
            { typeof(short), "short" },
            { typeof(ushort), "ushort" },
            { typeof(decimal), "decimal" },
            { typeof(float), "float" },
            { typeof(double), "double" },
        };

        /// <summary>
        /// This method takes a type and produces a proper full type name for it, in C# syntax, expanding generics properly.
        /// </summary>
        /// <param name="type">
        /// The type to produce the full type name for.
        /// </param>
        /// <returns>
        /// The type name for <paramref name="type"/> as a String.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="type"/> is <c>null</c>.</para>
        /// </exception>
        public static string ToStringEx(this Type type)
        {
            if (type == null)
                throw new ArgumentNullException("type");

            string result;
            if (CSharpKeywords.TryGetValue(type, out result))
                return result;

            if (type.IsGenericType)
            {
                if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    Type underlyingType = type.GetGenericArguments()[0];
                    return String.Format(CultureInfo.InvariantCulture, "{0}?", underlyingType.ToStringEx());
                }

                Debug.Assert(type.FullName != null, "type.FullName cannot be null here");
                string baseName = type.FullName.Substring(0, type.FullName.IndexOf("`", StringComparison.Ordinal));
                return baseName + "<" + String.Join(", ", (from paramType in type.GetGenericArguments()
                                                           select paramType.ToStringEx()).ToArray()) + ">";
            }

            result = type.FullName;

            // remove System. if the type is in the System namespace
            Debug.Assert(result != null, "result cannot be null here");
            if (result.StartsWith("System.", StringComparison.Ordinal) && result.LastIndexOf(".", StringComparison.Ordinal) == 6)
                result = result.Substring(7);

            return result;
        }

        /// <summary>
        /// Creates an instance of the specified type using the constructor that best
        /// matches the specified parameters.
        /// </summary>
        /// <param name="type">
        /// The type of object to create.
        /// </param>
        /// <param name="args">
        /// An array of arguments that match in number, order, and type the parameters
        /// of the constructor to invoke. If args is an empty array or null, the constructor
        /// that takes no parameters (the default constructor) is invoked.
        /// </param>
        /// <returns>
        /// A reference to the newly created object.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="type"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para><paramref name="type"/> is an open generic type
        /// (that is, the <see cref="Type.ContainsGenericParameters"/> property returns <c>true</c>).
        /// </para>
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// <para><paramref name="type"/> cannot be a <see cref="TypeBuilder"/>.</para>
        /// <para>- or -</para>
        /// <para>Creation of <see cref="TypedReference"/>, <see cref="ArgIterator"/>, <see cref="Void"/>,
        /// and <see cref="RuntimeArgumentHandle"/> types, or arrays of those types,
        /// is not supported.</para>
        /// <para>- or -</para>
        /// <para>The constructor that best matches <paramref name="args"/> has varargs arguments.</para>
        /// </exception>
        public static object CreateInstance(this Type type, params object[] args)
        {
            if (type == null)
                throw new ArgumentNullException("type");

            return Activator.CreateInstance(type, args);
        }

        /// <summary>
        /// Creates an instance of the specified type using the constructor that best
        /// matches the specified parameters.
        /// </summary>
        /// <typeparam name="T">
        /// The type to cast and return the instance as.
        /// </typeparam>
        /// <param name="type">
        /// The type of object to create.
        /// </param>
        /// <param name="args">
        /// An array of arguments that match in number, order, and type the parameters
        /// of the constructor to invoke. If args is an empty array or null, the constructor
        /// that takes no parameters (the default constructor) is invoked.
        /// </param>
        /// <returns>
        /// A reference to the newly created object.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="type"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para><paramref name="type"/> is an open generic type
        /// (that is, the <see cref="Type.ContainsGenericParameters"/> property returns <c>true</c>).
        /// </para>
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// <para><paramref name="type"/> cannot be a <see cref="TypeBuilder"/>.</para>
        /// <para>- or -</para>
        /// <para>Creation of <see cref="TypedReference"/>, <see cref="ArgIterator"/>, <see cref="Void"/>,
        /// and <see cref="RuntimeArgumentHandle"/> types, or arrays of those types,
        /// is not supported.</para>
        /// <para>- or -</para>
        /// <para>The constructor that best matches <paramref name="args"/> has varargs arguments.</para>
        /// </exception>
        public static object CreateInstance<T>(this Type type, params object[] args)
        {
            if (type == null)
                throw new ArgumentNullException("type");

            return (T)Activator.CreateInstance(type, args);
        }
    }
}